<script setup lang="ts">
import {ref, defineProps, computed, onMounted, onUpdated, nextTick} from 'vue'

let start_offset = ref(0)
let start = ref(0)
let end = ref(0)
const container_dom = ref()
const item_dom = ref()
const position = ref([{index: 0, height: 0, top: 0, bottom: 0}])
let list = ref<any[]>([])
const props = defineProps({
  list_data: {
    type: Array,
    default: []
  },
  estimated_item_size: {
    type: Number,
    default: 50
  },
  buffer_scale: {
    type: Number,
    default: 1
  },
  content_height: {
    type: Number,
    default: 800
  }
})
list.value = props.list_data.map((item, index) => {
  return {
    _index: index, item
  }
})

const list_height = computed(() => {
  return position.value[position.value.length - 1].bottom
})
const visible_list = computed(() => {
  const start_index = start.value - above_count.value
  const end_index = Math.min(end.value + below_count.value, list.value.length)
  return list.value.slice(start_index, end_index)
})
const visible_count = computed(() => {
  return Math.ceil(props.content_height / props.estimated_item_size)
})
const get_transform = computed(() => {
  return `translate3d(0,${start_offset.value}px,0)`;
})
const above_count = computed(() => {
  return Math.min(start.value, /*props.buffer_scale * visible_count.value*/2)
})
const below_count = computed(() => {
  return Math.min(position.value.length - end.value, /*props.buffer_scale * visible_count.value*/2)
})

onMounted(() => {
  end.value = start.value + visible_count.value
})

onUpdated(() => {
  nextTick(() => {
    item_dom.value.forEach((node => {
      let rect = node.getBoundingClientRect()
      let height = rect.height
      let index = +node.id
      let estimated_height = position.value[index].height
      let D_value = estimated_height - height
      if (D_value) {
        position.value[index].height = height
        position.value[index].bottom -= D_value
        for (let k = index + 1; k < position.value.length; k++) {
          position.value[k].top = position.value[k - 1].bottom
          position.value[k].bottom -= D_value
        }
      }
    }))
  })
})

function scroll_event() {
  const scrollTop = container_dom.value.scrollTop
  start.value = binarySearch(position.value, scrollTop)
  end.value = start.value + visible_count.value
  if (start.value) {
    start_offset.value = position.value[start.value - 1].bottom
  } else {
    start_offset.value = 0
  }
}

function init_position() {
  position.value = list.value.map(({}, index) => {
    const {estimated_item_size} = props
    return {
      index,
      height: estimated_item_size,
      top: index * estimated_item_size,
      bottom: (index + 1) * estimated_item_size
    }
  })
}

function binarySearch(list, value): number {
  let start = 0;
  let end = list.length - 1;
  let tempIndex: number | null = null;
  while (start <= end) {
    let midIndex = parseInt((start + end) / 2);
    let midValue = list[midIndex].bottom;
    if (midValue === value) {
      return midIndex + 1;
    } else if (midValue < value) {
      start = midIndex + 1;
    } else if (midValue > value) {
      if (tempIndex === null || tempIndex > midIndex) {
        tempIndex = midIndex;
      }
      end = end - 1;
    }
  }
  // @ts-ignore
  return tempIndex;
}

init_position()
</script>

<template>
  <div class="container" :style="{ 'max-height': `${props.content_height}px` }" ref="container_dom"
       @scroll="scroll_event">
    <div class="phantom" :style="{ 'height': `${list_height}px` }"></div>
    <div class="list" :style="{ 'transform': get_transform }">
      <div v-for="(item, index) in visible_list"
           ref="item_dom"
           :id="item?._index"
           :key="index">
        <slot :item="item?.item" :index="index"></slot>
      </div>
    </div>
  </div>
</template>

<style scoped>
.container {
  height: 100%;
  overflow: auto;
  position: relative;
  -webkit-overflow-scrolling: touch;
  -ms-overflow-style: none;
}

.container::-webkit-scrollbar {
  display: none;
}

.phantom {
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  z-index: -1;
}

.list {
  position: absolute;
  left: 0;
  top: 0;
  right: 0;
}
</style>
